package com.kymatrix.kycarbon.xsmd.common.filter;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.kymatrix.kycarbon.xsmd.common.annotation.ApiAccessLog;
import com.kymatrix.kycarbon.xsmd.common.business.common.ResultResp;
import com.kymatrix.kycarbon.xsmd.common.util.ServletUtils;
import com.kymatrix.kycarbon.xsmd.common.constants.Constant;
import com.kymatrix.kycarbon.xsmd.common.springsecurity.SecurityUtils;
import com.kymatrix.kycarbon.xsmd.common.util.Exceptions;
import com.kymatrix.kycarbon.xsmd.system.api.dto.SaveOperateLogDto;
import com.kymatrix.kycarbon.xsmd.system.api.service.OperateLogApi;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;


/**
 * API 访问日志 Filter
 *
 * 目的：记录 API 访问日志到数据库中
 */
@Component
@Slf4j
@Order(Constant.WebFilterOrderConstants.OPERATE_LOG_FILTER)
public class OperateLogFilter extends OncePerRequestFilter {

    @Resource
    private OperateLogApi operateLogApi;

    @Override
    @SuppressWarnings("NullableProblems")
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        LocalDateTime beginTime = LocalDateTime.now();
        Map<String, String> queryString = null;
        String requestBody = null;
        try {
            // 继续过滤器
            filterChain.doFilter(request, response);
            queryString = ServletUtils.getParamMap(request);
            requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;
            // 正常执行，记录日志
            createApiAccessLog(request, beginTime, queryString, requestBody, null);
        } catch (Exception ex) {
            // 异常执行，记录日志
            createApiAccessLog(request, beginTime, queryString, requestBody, ex);
            throw ex;
        }
    }

    private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime,
                                    Map<String, String> queryString, String requestBody, Exception ex) {
        SaveOperateLogDto dto = new SaveOperateLogDto();
        try {
            boolean saveFlag = buildApiAccessLog(dto, request, beginTime, queryString, requestBody, ex);
            if (!saveFlag) {
                return;
            }
            operateLogApi.saveOperateLog(dto);
        } catch (Throwable th) {
            log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), JSON.toJSONString(dto), th);
        }
    }

    private boolean buildApiAccessLog(SaveOperateLogDto dto, HttpServletRequest request, LocalDateTime beginTime,
                                      Map<String, String> queryString, String requestBody, Exception ex) {
        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(Constant.ATTRIBUTE_HANDLER_METHOD);
        ApiAccessLog accessLogAnnotation = null;
        if (handlerMethod != null) {
            accessLogAnnotation = handlerMethod.getMethodAnnotation(ApiAccessLog.class);
            if (accessLogAnnotation != null && BooleanUtil.isFalse(accessLogAnnotation.enable())) {
                return false;
            }
        }
        // 处理用户信息
        dto.setUserId(SecurityUtils.getLoginUserId());
        dto.setUsername(SecurityUtils.getLoginUsername());
        // 设置访问结果
        ResultResp<?> result = ServletUtils.getResultResp(request);
        if (result != null) {
            dto.setResultCode(result.getCode());
            dto.setResultMsg(result.isSuccess() ? JSON.toJSONString(result.getResult()) : result.getMessage());
        }
        dto.setExceptionInfo(Exceptions.getStackTraceAsString(ex));
        // 设置请求字段
        dto.setRequestUrl(request.getRequestURI());
        dto.setRequestMethod(request.getMethod());
        dto.setUserAgent(ServletUtils.getUserAgent(request));
        dto.setUserIp(ServletUtils.getClientIP(request));
        Map<String, Object> requestParams = MapUtil.<String, Object>builder()
                .put("query", queryString)
                .put("body", requestBody).build();
        dto.setRequestParams(JSON.toJSONString(requestParams));
        // 持续时间
        dto.setBeginTime(beginTime);
        dto.setEndTime(LocalDateTime.now());
        dto.setElapsedTime(LocalDateTimeUtil.between(beginTime, LocalDateTime.now(), ChronoUnit.MILLIS));

        // 操作模块
        if (handlerMethod != null) {
            Api api = handlerMethod.getBeanType().getAnnotation(Api.class);
            ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
            String operateModule = api != null ? StrUtil.nullToDefault(Arrays.toString(api.tags()), "") : null;
            String operateName = apiOperation != null ? apiOperation.value() : "";
            dto.setType(operateModule);
            dto.setSubType(operateName);
        }
        return true;
    }

}
